package top.bulk.service.impl;

import lombok.NonNull;
import lombok.SneakyThrows;
import lombok.extern.slf4j.Slf4j;
import org.apache.ibatis.session.SqlSession;
import org.mybatis.spring.SqlSessionTemplate;
import org.springframework.jdbc.datasource.DataSourceTransactionManager;
import org.springframework.stereotype.Service;
import org.springframework.transaction.PlatformTransactionManager;
import org.springframework.transaction.TransactionDefinition;
import org.springframework.transaction.TransactionStatus;
import org.springframework.transaction.annotation.Transactional;
import org.springframework.transaction.support.TransactionCallbackWithoutResult;
import org.springframework.transaction.support.TransactionTemplate;
import top.bulk.entity.User;
import top.bulk.mapper.UserMapper;
import top.bulk.service.UserService;

import javax.annotation.Resource;
import java.sql.Connection;
import java.sql.SQLException;
import java.text.SimpleDateFormat;
import java.util.ArrayList;
import java.util.Date;
import java.util.List;
import java.util.concurrent.CompletableFuture;
import java.util.concurrent.ExecutorService;
import java.util.concurrent.Executors;

/**
 * impl
 *
 * @author 散装java
 * @version 1.0.0
 * @date 2025-02-15
 */
@Service
@Slf4j
public class UserServiceImpl implements UserService {

    @Resource
    private UserMapper userMapper;

    @Resource
    private TransactionTemplate transactionTemplate;

    @Resource
    private SqlSessionTemplate sqlSessionTemplate;

    /**
     * 声明式事务，通过注解的方式，直接添加到方法上面
     * 底层原理，通过AOP的方式，拦截到方法，然后进行事务的开启和提交。
     * 详见 DataSourceTransactionManager#doBegin
     *
     * @see DataSourceTransactionManager#doBegin(Object, TransactionDefinition)
     */
    @Override
    @Transactional(rollbackFor = RuntimeException.class)
    public void declarativeTransaction() {

        log.info("declarativeTransaction");
        // 获取当前日期字符串
        User user = new User("bulkJava", new SimpleDateFormat("yyyy-MM-dd HH:mm:ss").format(new Date()));
        userMapper.insert(user);
        log.info("insert user:{}", user);

        // 模拟异常，可以触发事务，进行回滚- 表现现象为，日志有打印带着id的user，而数据库中没有对应id的数据。且下一次正常插入的表现为，数据库的下一条数据id跳跃。
        // throw new RuntimeException("模拟异常");
    }

    /**
     * 编程式事务，通过TransactionTemplate的方式
     * 其和声明式事务的比的好处是，更好的控制事务的范围。坏处为，多写代码复杂度高
     */
    @Override
    public void programmaticTransaction() {
        log.info("programmaticTransaction");
        // 前面可以模拟其非事务管理业务
        // ...

        transactionTemplate.execute(new TransactionCallbackWithoutResult() {
            @Override
            protected void doInTransactionWithoutResult(@NonNull TransactionStatus status) {
                try {
                    User user = new User("bulkJava", new SimpleDateFormat("yyyy-MM-dd HH:mm:ss").format(new Date()));
                    userMapper.insert(user);
                    log.info("insert user:{}", user);
                    // 可以在这里添加更多的数据库操作

                    // 模拟异常
                    throw new RuntimeException("模拟异常");
                } catch (Exception e) {
                    // 出现了异常进行事务回滚
                    status.setRollbackOnly();
                    // 可以根据业务决定是否吞异常
                    throw e;
                }
            }
        });

        // 后面可以去进行一些其他非事务管理的业务
        List<User> users = userMapper.queryListLatest10();
        users.forEach(user -> log.info("insert user:{}", user));
    }

    /**
     * 多线程的环境下，进行事务的回滚
     * 核心是自己控制事务，而不能用spring默认的，因为默认的是基于线程上下文控制的，无法做到多线程的回滚
     * <p>
     * 酌情考虑是否使用，因为会造成大事务以及大事务回滚问题，影响mysql的性能
     */
    @Override
    @SneakyThrows
    public void theadTransaction() {
        SqlSession sqlSession = sqlSessionTemplate.getSqlSessionFactory().openSession();
        ExecutorService executorService = Executors.newFixedThreadPool(5);
        List<CompletableFuture<Void>> list = new ArrayList<>();

        try (Connection connection = sqlSession.getConnection()) {
            // 关闭自动提交
            connection.setAutoCommit(false);
            UserMapper userMapper2 = sqlSession.getMapper(UserMapper.class);

            for (int i = 0; i < 5; i++) {
                int finalI = i;
                // 多线程插入数据
                CompletableFuture<Void> future = CompletableFuture.runAsync(() -> {
                    User user = new User("bulkJava" + finalI, new SimpleDateFormat("yyyy-MM-dd HH:mm:ss").format(new Date()));
                    userMapper2.insert(user);
                    log.info("theadTransaction user:{}", user);
                    // 让其中的一个线程执行过程中抛出异常
                    if (finalI == 3)
                        throw new RuntimeException("模拟异常");
                }, executorService);
                list.add(future);
            }

            // 拿到执行结果
            CompletableFuture<Void> future = CompletableFuture.allOf(list.toArray(new CompletableFuture[0]));

            // 获取异常，并且回滚
            future.exceptionally(e -> {
                log.error("future 异常：{}", e.getMessage(), e);
                // 回滚
                try {
                    connection.rollback();
                } catch (SQLException throwables) {
                    throwables.printStackTrace();
                }
                return null;
            });
            // 等待所有线程执行完毕
            future.get();
            // 提交
            connection.commit();
        }

    }
}
